//@author A0080384E
#include "Logic.h"

//constructor
Logic::Logic(){
	logicLogger.setFileName(FILE_NAME_LOGIC_LOGGER);
}


//this function is to determine the type of the command that user wants 
//pre-con: the user input string of the command
//post-con: the specific CommandType
Storage::CommandType Logic::determineCommandType(QString commandTypeString){

	if (util.equalsIgnoreCase(commandTypeString, "add")) {
		return Storage::CommandType::ADD;
    }
	if (util.equalsIgnoreCase(commandTypeString, "modify")){
		return Storage::CommandType::MODIFY;
	}
	if (util.equalsIgnoreCase(commandTypeString, "delete")) {
		return Storage::CommandType::DELETE;
	} 
	if (util.equalsIgnoreCase(commandTypeString, "search")) {
		return Storage::CommandType::SEARCH;
	}
	if (util.equalsIgnoreCase(commandTypeString, "save")){
		return Storage::CommandType::SAVE;
	}
	if (util.equalsIgnoreCase(commandTypeString, "display")) {
		return Storage::CommandType::DISPLAY;
	} 
	if (util.equalsIgnoreCase(commandTypeString, "undo")) {
		return Storage::CommandType::UNDO;
	} 
	if (util.equalsIgnoreCase(commandTypeString, "exit")) {
		return Storage::CommandType::EXIT;
	} 
	if (util.equalsIgnoreCase(commandTypeString,"complete")) {
		return Storage::CommandType::COMPLETE;
	}
	return Storage::CommandType::INVALID;
	
}


// this function is to execute the user's command
//pre-con: the whole user input string 
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::executeCommand(QString userCommand){
	LogicReply logicReply;	   

	logicLogger.logInfo(userCommand.toStdString());
	try {
		QString commandTypeString = getFirstWord(userCommand);
		Storage::CommandType commandType = determineCommandType(commandTypeString);
		QString parameters = removeFirstWord(userCommand);

		switch (commandType) {
			case Storage::CommandType::ADD:
				logicReply = executeAdd(parameters);
				break;
    
			case Storage::CommandType::MODIFY:		
				logicReply = executeModify(parameters);
				break;

			case Storage::CommandType::DELETE:
				logicReply = executeDelete(parameters);
				break;

			case Storage::CommandType::DISPLAY:
				logicReply = executeDisplay(parameters);
				break;

			case Storage::CommandType::SAVE:
				logicReply = executeSave();
				break;

			case Storage:: CommandType::SEARCH:
				logicReply = executeSearch(parameters); 
				break;

			case Storage::CommandType::UNDO:
				logicReply = executeUndo();		 
				break;

			case Storage::CommandType::COMPLETE:
				logicReply = executeComplete(parameters);
				break;

			case Storage::CommandType::EXIT:
				executeSave();
				exit(0);

			default:
				throw InvalidInputException(MESSAGE_VALID_FORMAT_PROMPT);
				break;
		}
	} 
	catch(InvalidInputException& e) {
		logicReply.setFeedback(e.what());
        logicReply.setSignalType(LogicReply::SignalType::NONE);
		logicLogger.logInfo(e.what());
	}

	return logicReply;
}

//this function is to modify the component of an existing task
//pre-con: the user input string string containing the reference number of the task to be modified,the component to be modified and the new content of the modified component
//post-con: an LogicReply object containing feedback string and signaltype
LogicReply Logic::modifyProperty(int taskRefNumber, QString userInput){
	LogicReply logicReply;
	QString component, modifyDetails;

	try {
		parser.retrieveParamModify(userInput, component, modifyDetails);
		logicReply.setFeedback(storage.modifyCommand(taskRefNumber, component, modifyDetails));
	}
	catch(InvalidInputException &e) {
		logicReply.setFeedback(e.what());
		logicLogger.logInfo(e.what());
	}

	logicReply.setSignalType(LogicReply::NONE);
	return logicReply;
}

//this function is to delete a specific task
//pre-con: an integer of the reference number of the task to be deleted
//post-con: post-con: an LogicReply object containing feedback string and signaltype 
LogicReply Logic::deleteTask(int taskNumber){
    LogicReply logicReply;
	
	logicReply.setFeedback(storage.deleteCommand(taskNumber));
	logicReply.setSignalType(LogicReply::NONE);
	return logicReply;
}

//this function is to add a new task
//pre-con: a string of parameters containing the information of the new task to be added
//post-con: an LogicReply object containing feedback string and signaltype
LogicReply Logic::executeAdd(QString parameters){
    Task newTask;
    QDate newDate;
    Task::TaskType newTaskType;
	LogicReply logicReply;
	
	if(parameters.size() == 0){
		throw InvalidInputException(MESSAGE_VALID_FORMAT_PROMPT);
	}

    newTask = taskManager.create(parameters);
    newTaskType = newTask.getTaskType();
    logicReply.setFeedback(storage.addCommand(newTask));
	logicReply.setSignalType(LogicReply::NONE);

    if (newTaskType != Task::FLOATING) {
        newDate = newTask.getTaskDate();
        logicReply.setTaskList(storage.displayDate(newDate));
        logicReply.setDisplayType(LogicReply::DisplayType::DISPLAY_DATE);
    }

    return logicReply;
}

//this function is to searching for a specific task to be modifed or prompt for a keyword if the user forgets to type
//pre-con: a string of parameters containing the keyword of the tasks to be modifed
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::executeModify(QString parameters){
    LogicReply logicReply;
	
	logicReply.setSignalType(LogicReply:: CALL_MODIFY);

    if (parameters.isEmpty()) {
        logicReply.setFeedback(MESSAGE_PROMPT_FOR_KEYWORD);
        logicReply.setSignalType(LogicReply::CALL_SEARCH_MODIFY);
    }
	else if(storage.searchCommand(parameters).isEmpty()) {
		logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
	}
	else {
		logicReply.setFeedback(MESSAGE_SEARCH_SELECT);
		logicReply.setTaskList(storage.searchCommand(parameters));
		logicReply.setDisplayType(LogicReply::DisplayType::DISPLAY_ALL);
	}

	return logicReply;
}

// this function is to search for the keyword of a specific task to be deleted or prompt for keyword if the user forgets to type the keyword
//pre-con: a string of parameters containing the keyword of the tasks to be deleted
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::executeDelete(QString parameters){
    LogicReply logicReply;
    logicReply.setSignalType(LogicReply::NONE);

	if ((parameters.isEmpty()) || parameters == " ") {
		logicReply.setFeedback(MESSAGE_PROMPT_FOR_KEYWORD);
        logicReply.setSignalType(LogicReply::CALL_SEARCH_DELETE);
    } 
	else if (storage.searchCommand(parameters).isEmpty()) {
		logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
	}
	else if (storage.searchCommand(parameters).size() == 1) {
		int taskNumber = storage.searchCommand(parameters)[0].getTaskRef();
		logicReply.setFeedback(storage.deleteCommand(taskNumber));	
	    logicReply.setSignalType(LogicReply:: NONE);
	}
	else {
		logicReply.setFeedback(MESSAGE_SEARCH_SELECT);
        logicReply.setTaskList(storage.searchCommand(parameters));
        logicReply.setDisplayType(LogicReply::DisplayType::DISPLAY_ALL);
        logicReply.setSignalType(LogicReply::CALL_DELETE);
	}

	return logicReply;
}

//this function is to determine the type of tasks that the user wants to display
//pre-con: a string of the display type
//post-con: an DisplayType object
LogicReply::DisplayType Logic::determineDisplayType(QString displayTypeString){

	if (util.equalsIgnoreCase(displayTypeString, "floating")) {
		return LogicReply::DisplayType::DISPLAY_FLOAT;
    }
	if (util.equalsIgnoreCase(displayTypeString, "deadline")){
		return LogicReply::DisplayType::DISPLAY_DEADLINE;
	}
	if (util.equalsIgnoreCase(displayTypeString, "meeting")) {
		return LogicReply::DisplayType::DISPLAY_MEETING;
	}
	if ((util.equalsIgnoreCase(displayTypeString, "all")) || 
		(displayTypeString == "") || 
		(displayTypeString == " ")) {
		return LogicReply::DisplayType::DISPLAY_ALL;
	}
	if (util.equalsIgnoreCase(displayTypeString, "date")){
		return LogicReply:: DisplayType::DISPLAY_DATE;
	}
	if (util.equalsIgnoreCase(displayTypeString, "urgent")){
		return LogicReply:: DisplayType::DISPLAY_URGENT;
	}
	if (util.equalsIgnoreCase(displayTypeString,"overdue")){
		return LogicReply:: DisplayType::DISPLAY_OVERDUE;
	}
		
	return LogicReply::DisplayType:: DISPLAY_NONE;
}

//this function is to display the tasks that are required by the user
//pre-con: a string of parameters containing the type of the tasks required to be displayed
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::executeDisplay(QString parameters) {
    LogicReply logicReply;
    QString type, date;

	try {
		type = getFirstWord(parameters);
		date = removeFirstWord(parameters);
		LogicReply::DisplayType displayType = determineDisplayType(type);

		switch (displayType){
			case LogicReply::DISPLAY_FLOAT:
				logicReply = displayFloating();
				break;
			
			case LogicReply::DISPLAY_DEADLINE:
				logicReply = displayDeadline();
				break;
			
			case LogicReply::DISPLAY_MEETING:
				logicReply = displayMeeting();
				break;
			
			case LogicReply::DISPLAY_ALL:
				logicReply = displayAll();
				break;
			
			case LogicReply::DISPLAY_DATE:
				logicReply = displayDate(date);
				break;
			
			case LogicReply::DISPLAY_URGENT:
				logicReply = displayUrgent();
				break;
			
			case LogicReply::DISPLAY_OVERDUE:
				logicReply = displayOverdue();
				break;
			
			case LogicReply::DISPLAY_NONE:
				logicReply.setSignalType(LogicReply::SignalType::NONE);
				break;
			
			default:
				logicReply.setSignalType(LogicReply::SignalType::NONE);
				logicReply.setFeedback(MESSAGE_INVALID_FORMAT);
		};
	} 
	catch(InvalidInputException &e) {
		logicReply.setFeedback(e.what());
		logicReply.setSignalType(LogicReply::SignalType::NONE);
		logicLogger.logInfo(e.what());
	}

    return logicReply;
}
//this function is to display the floating tasks
//pre-con: nil
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::displayFloating() {
    LogicReply logicReply;
    
	QVector<Task> displayTask = storage.displayFloating();
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_FLOAT);
    }

    return logicReply;
}
//this function is to display the deadline tasks
//pre-con: nil
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::displayDeadline() {
    LogicReply logicReply;
    
	QVector<Task> displayTask = storage.displayDeadline();
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_DEADLINE);
    }

    return logicReply;

}

//this function is to display the meeting tasks
//pre-con: nil
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::displayMeeting() {
    LogicReply logicReply;

	QVector<Task> displayTask = storage.displayMeeting();
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_MEETING);
    }

    return logicReply;

}
//this function is to display all the tasks
//pre-con: nil
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic:: displayAll() {
    LogicReply logicReply;

	QVector<Task> displayTask = storage.displayAll();
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_ALL);
    }

    return logicReply;
}

//this function is to display the  tasks on a particular date
//pre-con: a parameter string containing the date required
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic:: displayDate(QString date){
    LogicReply logicReply;
	QDate getDate = parser.parseDate(date);

	QVector<Task> displayTask = storage.displayDate(getDate);
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_DATE);
    }
    return logicReply;
}

//this function is to display the urgent tasks
//pre-con: nil
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic:: displayUrgent(){
    LogicReply logicReply;

	QVector<Task> displayTask = storage.checkApproaching();
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_URGENT);
	}

    return logicReply;
}
//this function is to display the overdue tasks
//pre-con: nil
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic:: displayOverdue(){
    LogicReply logicReply;

	QVector<Task> displayTask = storage.checkOverdue();
    logicReply.setSignalType(LogicReply::NONE);
    
	if (displayTask.isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    }
	else {
		logicReply.setFeedback(MESSAGE_DISPLAY);
        logicReply.setTaskList(displayTask);
        logicReply.setDisplayType(LogicReply::DISPLAY_FLOAT);
    }

    return logicReply;
}

//this function is to search the tasks that contain the keyword given by the user
//pre-con: a string of parameters containing the keyword of the tasks 
//post-con: an LogicReply object containing feedback string, signaltype and a list of tasks if required
LogicReply Logic::executeSearch(QString parameters){
    LogicReply logicReply;
    logicReply.setSignalType(LogicReply::NONE);

    if(parameters.isEmpty()){
		logicReply.setFeedback(MESSAGE_PROMPT_FOR_KEYWORD);
        logicReply.setSignalType(LogicReply::SignalType::CALL_SEARCH);
	}
	else if(storage.searchCommand(parameters).isEmpty()) {
        logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
    } else {
        logicReply.setFeedback(MESSAGE_SEARCH);
        logicReply.setTaskList(storage.searchCommand(parameters));
        logicReply.setDisplayType(LogicReply::DisplayType::DISPLAY_ALL);
    }
	
	return logicReply;
}
//this function is to undo the previous action
//pre-con: nil
//post-con: an LogicReply object containing feedback string and signaltype
LogicReply Logic::executeUndo(){
    LogicReply logicReply;

	logicReply.setFeedback(storage.undoCommand());
	logicReply.setSignalType(LogicReply:: NONE);
	
	return logicReply;
}

// this function is to mark the task as completed
//pre-con: a parameter string containing the keyword of the task to be marked
//post-con: a logicReply object which contains the feedback string and the list of related task if required

LogicReply Logic::executeComplete(QString parameters) {
	LogicReply logicReply;
	logicReply.setSignalType(LogicReply::CALL_COMPLETE);

	if(parameters.isEmpty()){
		logicReply.setFeedback(MESSAGE_PROMPT_FOR_KEYWORD);
        logicReply.setSignalType(LogicReply::CALL_SEARCH_COMPLETE);
    }
	else if (storage.searchCommand(parameters).size() == 1){
		int taskNumber = storage.searchCommand(parameters)[0].getTaskRef();
		logicReply.setFeedback(storage.markComplete(taskNumber));	
	    logicReply.setSignalType(LogicReply::NONE);
	}
	else if(storage.searchCommand(parameters).isEmpty()) {
		logicReply.setFeedback(MESSAGE_TASK_NOT_AVAILABLE);
	}
	else {
		logicReply.setFeedback(MESSAGE_SEARCH);
        logicReply.setTaskList(storage.searchCommand(parameters));
        logicReply.setDisplayType(LogicReply::DISPLAY_ALL);
        logicReply.setSignalType(LogicReply::CALL_COMPLETE);
	}

	return logicReply;
}

// this function is to mark a specif task as compeleted
// pre-con: reference number of that task
// psot-con: a logicReply object which contains the feedback string

LogicReply Logic:: markCompletedTask(int taskNumber) {
    LogicReply logicReply;

	logicReply.setFeedback(storage.markComplete(taskNumber));
	logicReply.setSignalType(LogicReply:: NONE);
	return logicReply;
}

//this function is to save the session
//pre-con: nil
//post-con: a logicReply object which contains necessary feedback string
LogicReply Logic:: executeSave(){
    LogicReply logicReply;
	sessionManager.storeThisSession(storage.displayAll());
	
	logicReply.setFeedback(MESSAGE_SAVED);
	logicReply.setSignalType(LogicReply::NONE);
	
	return logicReply;
}
//this function is to load the session
//pre-con:nil
//post_con: session has been loaded
LogicReply Logic::loadSession(QVector<QString> &invalidTasks) {
    QString userFeedback;
    LogicReply logicReply;
	ostringstream sessionInfo;

    if (sessionManager.isSession(userFeedback, sessionInfo)) {
        QVector<Task> currSession = taskManager.loadSession(sessionInfo, invalidTasks);
        for (int i = 0; i < currSession.size(); i++) {
            QString temp = storage.addCommand(currSession[i]);
        }

        logicReply = updateUrgentOverdueTasks();
    }

    logicReply.setFeedback(userFeedback);

    return logicReply;
}

// this function is to check if there are any overdue tasks or urgent tasks
//pre-con: two vector task to store the list of required tasks
//post-con: nil
LogicReply Logic::updateUrgentOverdueTasks() {
	QVector<Task> overdueTasks = storage.checkOverdue();
	QVector<Task> approachingDeadlineTasks = storage.checkApproaching();
	QVector<Task> combinedTasks;

	for (int i = 0; i < overdueTasks.size(); i++) {
		combinedTasks.push_back(overdueTasks[i]);	
	}
	
	for (int i = 0; i < approachingDeadlineTasks.size(); i++) {
		combinedTasks.push_back(approachingDeadlineTasks[i]);
	}
	
	LogicReply logicReply;
	logicReply.setTaskList(combinedTasks);
	logicReply.setSignalType(LogicReply::NONE);
	logicReply.setDisplayType(LogicReply::DISPLAY_ALL);

	return logicReply;
}

//this function is to retrieve the first word of a string
//pre-con: a string 
//post-con: a string containing the first word only
QString Logic::getFirstWord(QString userCommand){
    if ((userCommand  == " ") || (userCommand.isEmpty())) {
        throw InvalidInputException(MESSAGE_VALID_FORMAT_PROMPT);
    }

	string commandTypeString = util.splitParameters(userCommand.toStdString())[0];
	return QString::fromStdString(commandTypeString);
}

//this function is to retrieve the rest of a string after removing the first word 
//pre-con: a string 
//post-con: a string containing the rest of a string after removing the first word 
QString Logic::removeFirstWord(QString userCommand){
    QString parameters = userCommand.right(getFirstWord(userCommand).size());
    
	if(!parameters.isEmpty()){
        parameters = parameters.trimmed();
    }
	
	return parameters;
}